package plg;

import tablaSimbolos.TablaSimbolos;
import java.util.ArrayList;
import java.util.StringTokenizer;
import tipoToken.TipoToken;
import tipoToken.Tipos;


/**
 *
 * @author PLG Grupo 6
 */
public class AnalizadorLexico {
    private static AnalizadorLexico Instancia_AL = null;
    private ArrayList listaTokens; // lista de token definitivos
    private StringTokenizer tokenizer;
    private String[] palabrasReservadas = inicializaReservadas();
    private TablaSimbolos tabla = new TablaSimbolos();
    int numLinea = 0;

    private AnalizadorLexico()
    {
        this.listaTokens = new ArrayList();
    }

    /**
     * Devuelve una única instancia del analizador léxico.
     * @return
     */
    public static AnalizadorLexico getAnalizadorLexico(){
        if (Instancia_AL == null)
            Instancia_AL = new AnalizadorLexico();
        return Instancia_AL;
    }

    public void inicializarAL(){
        this.listaTokens.clear();
    }

    /**
     *
     **/
    public void tokenizador(String entrada)
    {
        Token token; // guarda el token actual
        String lex;  // guarda el lexema
        //String para guardar todos los tokens leidor por tokenizer antes de ejecutar la comprobacion
        ArrayList listaTokensTemporal = new ArrayList();
        //Se crea el tokenizdor especificando los delimitadores
        tokenizer = new StringTokenizer(entrada, " .,:;+-*/()=!#$%&\t\n",true);
        //Se almacenan todos los tokens reconocidos en la lista de tokens temporal
        //que puede contenet errores de reconocimiento de tokens
        while(tokenizer.hasMoreTokens()){
            listaTokensTemporal.add(tokenizer.nextToken());
        }

        numLinea = 1;//Se pone el numero de linea a uno
        //Se recorre la lista de tokens temporal para construir la lista de tokens definitiva
        for(int currentPosition=0; currentPosition<listaTokensTemporal.size(); currentPosition++) {
            token = new Token();
            token.setLexema((String)listaTokensTemporal.get(currentPosition));
            if(!TipoToken.tokenIsDelim(token.getLexema()))
            {
                int newPosition = 0;
                if(TipoToken.tokenIsNumber(token.getLexema()))
                {//Verificar si el token entrante es un numero real o no
                    newPosition = TipoToken.tokenIsStartOfNumber(token.getLexema(), listaTokensTemporal, currentPosition);
                    if(newPosition != currentPosition)
                    {
                        lex = "";
                        for(int j = currentPosition; j <= newPosition; j++) {
                            lex = lex.concat((String)listaTokensTemporal.get(j));
                        }
                        token.setLexema(lex);
                        currentPosition = newPosition;
                    }
                }
                if(token.getLexema().startsWith("'"))
                {//El token recibido es comienzo de comentario. Hay que coger el comentario entero
                    newPosition = TipoToken.tokenIsStartOfComment(token.getLexema(), listaTokensTemporal, currentPosition);
                    if(newPosition != currentPosition)
                    {
                        lex = "";
                        for(int j = currentPosition; j <= newPosition; j++) {
                            lex = lex.concat((String)listaTokensTemporal.get(j));
                        }
                        token.setLexema(lex);
                        currentPosition = newPosition;
                    }
                }
                //El token recibido es comienzo de string. Hay que coger el string entero
                if(token.getLexema().startsWith("\"")) {
                    newPosition = (TipoToken.tokenIsStartOfString(token.getLexema(), listaTokensTemporal, currentPosition));
                    if(newPosition != currentPosition)
                    {
                        lex = new String();
                        for(int j = currentPosition; j <= newPosition; j++) {
                            lex += ((String)listaTokensTemporal.get(j));
                        }
                        token.setLexema(lex);
                        currentPosition = newPosition;
                    }
                }
                comprobarTipoToken(token,palabrasReservadas);
            }
            else
            {
                if(TipoToken.tokenIsOperator(token.getLexema()))
                {
                    token.setTipo(Tipos.OPERADOR);
                    addToTokenList(token);
                }
                else if(TipoToken.tokenIsAsignacion(token.getLexema()))
                {
                    token.setTipo(Tipos.ASIGNACION);
                    addToTokenList(token);
                }
                else if(TipoToken.tokenIsTypeSpecifier(token.getLexema()))
                {
                    token.setTipo(Tipos.TYPE_SPECIFIER);
                    addToTokenList(token);
                }
                else if(TipoToken.tokenIsSeparator(token.getLexema()))
                {
                    token.setTipo(Tipos.SEPARADOR);
                    addToTokenList(token);
                }
                else if(TipoToken.tokenIsNewLine(token.getLexema()))
                {
                    if(currentPosition < listaTokensTemporal.size()-1)
                    {
                        token.setTipo(Tipos.SALTO_LINEA);
                        addToTokenList(token);
                        numLinea++;//Como el token reconocido es nueva linea, se acutaliza el atributo numLinea
                    }
                    else
                        insertEndOfProgram(token);
                }
            }
        }
        System.out.println(numLinea);
    }

    /**
     * Comprueba el tipo del token recibido, se lo asigna en el atributo tipo y lo instroduce en la lista de tokens si procede
     * @param token
     */
    private void comprobarTipoToken(Token token,String[] reservadas)
    {

        if(TipoToken.tipoPalabraReservada(token.getLexema(),reservadas))
        {
            token.setTipo(Tipos.PALABRA_RESERVADA);
            addToTokenList(token);
        }
        else if(TipoToken.tipoIdentificador(token.getLexema()))
        {
            token.setTipo(Tipos.IDENTIFICADOR);
            token.getTipoToken().setPosicionTB(this.tabla.buscarOInsertar(token.getLexema()));
            addToTokenList(token);
        }
        else if(TipoToken.tipoOperadorComp(token.getLexema()) || TipoToken.tipoOperadorAsig(token.getLexema()))
        {
            token.setTipo(Tipos.OPERADOR);
            addToTokenList(token);
        }
        else if(TipoToken.tipoNumeroReal(token.getLexema()))
        {
            token.setTipo(Tipos.NUM_REAL);
            addToTokenList(token);
        }
        else if(TipoToken.tipoNumeroEntero(token.getLexema()))
        {
            token.setTipo(Tipos.NUM_ENTERO);
            addToTokenList(token);
        }
        else if(TipoToken.tipoComentario(token.getLexema()))
            token.setTipo(Tipos.COMENTARIO);
            //Los comentarios no se meten en la lista de tokens
        else if(TipoToken.tipoString(token.getLexema()))
        {
            token.setTipo(Tipos.STRING);
            addToTokenList(token);
        }
        else
        {//No concuerda con ningun tipo conocido, por lo tanto, hay que reportar un error lexico
            ErrorPackage.Error er = new ErrorPackage.Error();
            er.reportError("Token no reconocido:" + token.getLexema());
            //Introduce el token erroneo en la lista de tokens, con el tipo TokenError
            token.setTipo(Tipos.ERROR);
            addToTokenList(token);
        }
    }

    /**
     * Devuelve el ArrayList que contiene la lista de tokens reconocidos
     * @return
     */
    public ArrayList getListaTokens()
    {
        return this.listaTokens;
    }

    /**
     * Incializa el array de palabras reservadas de nuestra gramática
     */
    private String[] inicializaReservadas()
    {
        // creamos el array con todas las palabras reservadas
        return new String[]{"AS","DIM","COMMON","REDIM","SHARED","STATIC","INTEGER","STRING",
                                                "LONG","SINGLE","DOUBLE","TO","TYPE","END TYPE","CONST","IF","THEN",
                                                "END IF","ELSEIF","ELSE","WHILE","WEND","DO","LOOP","UNTIL","SELECT CASE",
                                                "END CASE","CASE","IS","CASE ELSE","FOR","STEP","NEXT","ON","GOTO",
                                                "GOSUB","LOCK","UNLOCK","REM","PRINT","INPUT","LOCATE","CLS","SCREEN",
                                                "PSET","RANDOMIZE TIMER","LINE","CIRCLE","STOP","COLOR","DEF FN","END DEF",
                                                "DECLARE FUNCTION","END","FUNCTION","READ","WRITE","ABS","abs","COS","cos",
                                                "LOG","log","LEN","len","RETURN","return","SQR","sqr","SWAP","swap",
                                                "DECLARE SUB","SUB","END SUB","TIMER","EXP","exp","ASC","asc","static",
                                                "LET","let","TAN","tan","RND","rnd","DATE$","TIME$","EOF","eof"};
    }

    /**
     * Inserta el token recibido en la lista de tokens como token de fin de entrada. token.lexema = "Fin" y token.tipo = TokenFin
     * @param token
     */
    private void insertEndOfProgram(Token token)
    {
        token.setTipo(Tipos.FIN);
        token.setLexema("Fin");
        addToTokenList(token);
    }

    /**
     * Inserta el token recibido en la lista de tokens, despues de hacer chequeo de errores en el caso de que el tipo del token recibido sea identificador o error
     * @param token
     */
    private void addToTokenList(Token token)
    {
        if(token.getTipoToken().dameTipo().equals(Tipos.IDENTIFICADOR) ||
           token.getTipoToken().dameTipo().equals(Tipos.ERROR))
            ErrorPackage.Error.errorCheck(token);
        this.listaTokens.add(token);
    }
}